
/* 

- datatable基本操作: https://blog.csdn.net/pengkobe123/article/details/19930055?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-0-19930055-blog-111935780.pc_relevant_multi_platform_whitelistv4&spm=1001.2101.3001.4242.1&utm_relevant_index=3

 */

using System;
using System.IO;
using System.Collections.Generic;
using OfficeOpenXml;  // dotnet add package Epplus
using System.Data;
using System.Text.RegularExpressions;

using Newtonsoft.Json;
// using CsvHelper;
// using CsvHelper.Configuration;
using System.Text;
using System.Globalization;
using UnityEngine;
using System.Linq;


namespace AppTableUtils
{

    public class TableUtils
    {

        # region 依赖`UnityEngine`的相关方法
        
        /// <summary>
        /// 跨平台读取csv数据, 注意`file_name`是在`BdUtils.persistant_data_dir_path`下的相对路径, 不应以`data`开头!
        /// </summary>
        /// <param name="file_name">文件名</param>
        /// <param name="primary_key">主键</param>
        /// <param name="priority_persistent_data">是否优先读取`persistent_data`</param>
        /// <returns></returns>
        public static DataTable read_csv_data_on_all_platform(string file_name, string primary_key = null, bool priority_persistent_data = false)
        {
            DataTable ret;

            // --- priority_persistent_data: 如果`file`存在, 则不再复制. 适用于存档类数据
            if (priority_persistent_data)
            {
                // bool only_read_from_persistent = false
                string _f_path = BdUtils.join(BdUtils.persistant_data_dir_path, file_name);
                if (File.Exists(_f_path))
                {
                    ret = TableUtils.read_csv(_f_path, primary_key: primary_key);
                    return ret;
                }
            }

            string f_path = BdUtils.join(BdUtils.data_table_dir_path, file_name);

            // --- 如果是安卓的话, 就先把csv从streaming的jar压缩包解压到persistent路径下, 然后正常读取即可
            if (BdTools.Flags.is_android_or_webgl)
            {
                
                string dirname = "data";
                f_path = BdUtils.andriod_copy_file_from_streaming_to_persistant(file_name, dirname);
            }
// #if !UNITY_EDITOR && UNITY_ANDROID
//             string dirname = "data";
//             f_path = BdUtils.andriod_copy_file_from_streaming_to_persistant(file_name, dirname);
// #endif
            // BdUtils.andriod_copy_file_from_streaming_to_persistant
            BdUtils.assert(File.Exists(f_path), $"*** on read_csv: `f_path[{f_path}]`不存在?");

            ret = TableUtils.read_csv(f_path, primary_key: primary_key);
            return ret;
        }
        # endregion

        // public 
        public static Dictionary<K, T> test_T<K, T>(K key, T value)
        {
            Dictionary<K, T> dc = new Dictionary<K, T>();
            dc.Add(key, value);
            return dc;
        }


        public static Dictionary<string, int> test_T(string key, int value)
        {
            Dictionary<string, int> dc = new Dictionary<string, int>();
            dc.Add(key, value);
            return dc;
        }

        public static string temp_csv_sep = "<temp_csv_sep>";  // csv临时分隔符, 用来兼容"\,"的反转义符
        public static string temp_csv_line_break = "<temp_csv_line_break>";  // csv临时分隔符, 用来兼容"\,"的反转义符

        public static string ReplaceComma(string input)
        {
            // string pattern = "(?<=\")[^\"]*?(?=\")";
            string pattern = @"(?<!\\)""(.*?)(?<!\\)""";
            string output = Regex.Replace(input, pattern, ReplaceCommaInString);
            return output;
        }

        public static string ReplaceCommaInString(Match match)
        {
            string value = match.Value;
            string replaced = value.Replace(",", "\\,");
            return replaced;
        }

        public static string ReplaceCommaInCsvCell(string input)
        {
            string pattern = "(?<=^\")(.*?)(?=\")|(?<!\\\\)(,)"; // 不匹配斜杠开头的字段和逗号
            string replacement = "$1\\\\,"; // 替换逗号为逗号和反斜杠组合

            string replacedString = Regex.Replace(input, pattern, match =>
            {
                string value = match.Value;
                if (value.StartsWith("\\"))
                {
                    // 如果字段以斜杠开头，则不进行替换
                    return value;
                }
                else
                {
                    // 否则进行替换
                    return Regex.Replace(value, ",", replacement);
                }
            });

            return replacedString;
        }

        static DataTable _read_table(string filePath, string primary_key = null, Dictionary<string, System.Type> type_dc = null, bool priority_persistent_data = false)
        {
            BdUtils.check_file_extension(filePath, table_extension_ls, raise_error: true);
            
            DataTable dt;
            if (filePath.EndsWith("csv"))
            {
                filePath = BdUtils.get_relative_sub_path(BdUtils.streaming_data_dir_path, filePath);
                BdUtils.assert(!Path.IsPathFullyQualified(filePath), $"csv不能为绝对路径! filePath: [{filePath}]");
                dt = TableUtils.read_csv_data_on_all_platform(filePath, primary_key: primary_key, priority_persistent_data: priority_persistent_data);
            }
            else
            {
                dt = TableUtils.read_excel(filePath, primary_key: primary_key);
            }
            return dt;
        }

        static string _replace_line_break_in_two_quote(string input)
        {
            string pattern = @"(?<="")(.*?)(?="")";
            string result = Regex.Replace(input, pattern, match =>
            {
                return match.Value.Replace("\r\n", " ").Replace("\n", " ");
            });

            return result;
        }
        
        static string replace_line_break_in_two_quote(string input)
        {
            
            // 定义正则表达式模式，匹配双引号之间的内容
            string pattern = @"""([^""]*)""";

            // 创建 Regex 对象
            Regex regex = new Regex(pattern);

            // 在文本中查找匹配项
            MatchCollection matches = regex.Matches(input);

            int end_output_i = 0;

            StringBuilder stringBuilder = new StringBuilder();
            // 遍历匹配项并打印出内容
            foreach (Match match in matches)
            {
                int start_i = match.Groups[1].Index;
                
                int length_i = match.Groups[1].Length;
                string value_i = match.Groups[1].Value;
                
                int end_i = length_i + start_i;

                // --- 把中间的补上
                if (start_i > end_output_i)
                {
                    stringBuilder.Append(input.Substring(end_output_i, start_i - end_output_i));
                }
                
                // BdUtils.log("--- match --- Index, Length:", match.Groups[1].Index, match.Groups[1].Length);
                // BdUtils.log("--- value:", match.Groups[1].Value, "--- text_value:", input.Substring(match.Groups[1].Index, match.Groups[1].Length));
                
                string matched_value = match.Groups[1].Value;

                var symbol_ls = new List<string>()
                {
                    "\r\n",
                    "\r",
                    "\n"
                };
                foreach (var _symbol in symbol_ls)
                    if (matched_value.Contains(_symbol))
                    {
                        matched_value = matched_value.Replace(_symbol, temp_csv_line_break);
                    }
                
                string symbol;
                symbol = ",";
                if (matched_value.Contains(symbol))
                {
                    matched_value = matched_value.Replace(symbol, temp_csv_sep);
                }
                symbol = "\t";
                if (matched_value.Contains(symbol))
                {
                    matched_value = matched_value.Replace(symbol, "  ");
                }

                // BdUtils.log("--- start_i, end_i:", start_i, end_i, "--- matched_value:", matched_value.Replace("\"", "<d>"));

                stringBuilder.Append(matched_value + "\"");

                end_output_i = end_i + 1;
            }
            
            if (end_output_i < input.Length - 1)
            {
                stringBuilder.Append(input.Substring(end_output_i, input.Length - end_output_i));
            }
            
            // BdUtils.log("--- end_output_i, input.Length:", end_output_i, input.Length, "--- stringBuilder:", stringBuilder.ToString());

            // // 匹配双引号之间的内容
            // string pattern = @"(?<="")([^""]*)(?="")";
            //
            // // 替换匹配到的内容中的换行符
            // string output = Regex.Replace(input, pattern, match => match.Value.Replace("\r\n", ", "));

            string output = stringBuilder.ToString();
            return output;
        }

        public static DataTable read_csv(string filePath, string primary_key = null, Dictionary<string, System.Type> type_dc = null)  //从csv读取数据返回table
        {
            BdUtils.check_file_extension(filePath, ".csv");

            var reader_encoding = FileEncodingUtils.GetEncoding(filePath);

            DataTable dt = new DataTable(filePath);
            using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
            {
                using (StreamReader sr = new StreamReader(fs, reader_encoding))
                {
                    //记录每次读取的一行记录
                    string strLine = "";
                    //记录每行记录中的各字段内容
                    string[] aryLine = null;
                    string[] tableHead = null;
                    //标示列数
                    int columnCount = 0;
                    //标示是否是读取的第一行
                    bool IsFirst = true;
                    
                    string allLines = sr.ReadToEnd();
                    allLines = replace_line_break_in_two_quote(allLines);
                    var line_ls = allLines.Split(new[]
                    {
                        "\r\n",
                        "\r",
                        "\n"
                    }, StringSplitOptions.None);
                    int lineCount = line_ls.Length;
                    
                    // 逐行读取CSV中的数据
                    int i_line = 0;
                    while (i_line < lineCount)
                    {
                        strLine = line_ls[i_line];
                        if (IsFirst == true)
                        {
                            IsFirst = false;

                            // --- 处理双引号中的逗号
                            var old_strLine = strLine;
                            if (strLine.Contains("\""))
                            {
                                // --- 处理单元格中的"\,"
                                strLine = strLine.Replace("\\,", TableUtils.temp_csv_sep);
                                // --- 处理双引号中的逗号
                                strLine = ReplaceComma(strLine);
                            }
                            if (strLine.Contains("\\,"))
                                strLine = strLine.Replace("\\,", TableUtils.temp_csv_sep);

                            // BdUtils.log("--- IsFirst, old_strLine: ", old_strLine, "==> strLine:", strLine);
                            
                            tableHead = strLine.Split(',');
                            columnCount = tableHead.Length;
                            
                            // BdUtils.log($"lineCount: {lineCount}, columnCount:", columnCount, " --- tableHead:", string.Join(",", tableHead));
                            
                            //创建列
                            for (int i = 0; i < columnCount; i++)
                            {
                                string header_i = tableHead[i];
                                header_i = header_i.Replace(TableUtils.temp_csv_sep, ",");
                                header_i = header_i.Trim('"');

                                DataColumn dc = new DataColumn(header_i);
                                dt.Columns.Add(dc);
                            }
                        }
                        else
                        {
                            var old_strLine = strLine;

                            if (strLine.Contains("\""))
                            {
                                // --- 处理单元格中的"\,"
                                strLine = strLine.Replace("\\,", TableUtils.temp_csv_sep);
                                // --- 处理双引号中的逗号
                                strLine = ReplaceComma(strLine);
                            }
                            if (strLine.Contains("\\,"))
                                strLine = strLine.Replace("\\,", TableUtils.temp_csv_sep);

                            // BdUtils.raise_error("这里暂停!");
                            aryLine = strLine.Split(',');
                            // if (strLine.EndsWith(","))
                            // {
                            //     Array.Resize(ref aryLine, aryLine.Length - 1);
                            // }
                            if (BdUtils.is_null(aryLine))
                            {
                                i_line += 1;
                                continue;
                            }

                            int k_line_null = 0;
                            foreach (var obj in aryLine)
                            {
                                if (BdUtils.is_null(obj))
                                    k_line_null += 1;
                            }
                            if (k_line_null == aryLine.Length)
                            {
                                i_line += 1;
                                continue;
                            }

                            BdUtils.assert(aryLine.Length >= columnCount, 
                                $"第[{i_line}]行的`strLine`切分后长度为[{aryLine.Length}], 不应该小于 columnCount[{columnCount}]!\nstrLine: [{strLine}],\ntableHead: {string.Join(",", tableHead)}");
                            // BdUtils.log("--- i_line:", i_line, ", aryLine.Length:", aryLine.Length,
                            //     $"is_null: {BdUtils.is_null(aryLine[0])}", " --- aryLine:", string.Join(",", aryLine));
                            DataRow dr = dt.NewRow();

                            int count_null = 0;
                            for (int j = 0; j < columnCount; j++)
                            {
                                string value = aryLine[j];
                                if (BdUtils.is_null(value) || value == "")
                                    count_null++;

                                // --- 处理单元格中的"\,"和"\n"
                                value = value.Replace(TableUtils.temp_csv_sep, ",");
                                value = value.Replace(TableUtils.temp_csv_line_break, "\n");
                                value = value.Trim('"');

                                dr[j] = value;
                            }

                            if (count_null != columnCount)  // 去除空行
                                dt.Rows.Add(dr);
                        }
                        
                        i_line += 1;
                    }
                    if (aryLine != null && aryLine.Length > 0)
                    {
                        dt.DefaultView.Sort = tableHead[0] + " " + "asc";
                    }
                    sr.Close();
                    fs.Close();

                    if (primary_key != null)
                    {
                        if (BdUtils.is_integer(primary_key))
                            dt.PrimaryKey = new DataColumn[] { dt.Columns[(int)BdUtils.conv_to_int(primary_key)] };
                        else
                        {
                            BdUtils.assert(dt.Columns.Contains(primary_key.ToString()), $"表格[{dt.TableName}]指定主键列名[{primary_key.ToString()}]时出错!");
                            dt.PrimaryKey = new DataColumn[] { dt.Columns[primary_key.ToString()] };
                        }
                        // BdUtils.log(dt.Columns.Contains(primary_key.ToString()));
                        // BdUtils.log("primary_key --- ", primary_key, BdUtils.get_type(primary_key), "~~~", dt.PrimaryKey, dt.Columns);
                    }

                    return dt;
                }
            }
        }

        public static DataTable read_excel(string file_path, object primary_key = null, string table_name = null)
        {
            if (!Path.IsPathFullyQualified(file_path))
                file_path = BdUtils.join(BdUtils.data_table_dir_path, file_path);
            // BdUtils.assert(File.Exists(file_path), $"*** 路径{file_path}不存在!");

            if (!File.Exists(file_path))
            {
                string msg = $"*** 路径{file_path}不存在!";
                throw new Exception(msg);
            }

            using (var package = new ExcelPackage(new FileInfo(file_path)))
            {
                // foreach (var sheet in package.Workbook.Worksheets)
                // {
                //     BdUtils.log(" --- sheet:", sheet, package.Workbook.Worksheets[sheet.Name]);
                // }
                // BdUtils.log(" --- package.Workbook.Worksheets.Count:", package.Workbook.Worksheets.Count, package.Workbook.Worksheets);
                BdUtils.assert(package.Workbook.Worksheets.Count, "package.Workbook.Worksheets.Count必须大于0!");

                // 读取sheet
                var worksheet = package.Workbook.Worksheets[1]; // 这个下标很奇怪, 旧版本是`1`, 新版本是`0`
                if (table_name == null)
                    table_name = file_path;
                var dt = new DataTable(table_name);
                var startRow = worksheet.Dimension.Start.Row;
                var endRow = worksheet.Dimension.End.Row;
                var startCol = worksheet.Dimension.Start.Column;
                var endCol = worksheet.Dimension.End.Column;
                for (int col_i = startCol; col_i <= endCol; col_i++)
                {
                    // if (type_dc != null && type_dc.ContainsKey(col))
                    //     dt.Columns.Add(col, type_dc[col]);
                    // else
                    //     dt.Columns.Add(col);

                    dt.Columns.Add(worksheet.Cells[startRow, col_i].Value.ToString());
                }
                
                int primary_col_index = -1;

                string primary_name = "";
                if (primary_key != null)
                {
                    if (BdUtils.is_integer(primary_key))
                    {
                        primary_name = dt.Columns[BdUtils.conv_to_int(primary_key)].ColumnName;
                    }
                    else
                    {
                        primary_name = BdUtils.conv_to_string(primary_key);
                    }
                    primary_col_index = dt.Columns.IndexOf(primary_name);
                }
                
                for (int row = startRow + 1; row <= endRow; row++)
                {
                    var dr = dt.NewRow();

                    // BdUtils.log("--- primary_name:", primary_name, ", primary_col_index:", primary_col_index);

                    // --- 主键为ID时, 将自动忽略无法解析成正整数的行
                    if (primary_name == "id" || primary_name == "ID")
                        if (primary_col_index != -1)
                        {
                            // BdUtils.log($"row, primary_col_index: {(row, primary_col_index)}");
                            object primary_value =  worksheet.Cells[row, primary_col_index + 1].Value;
                            int flag = BdUtils.conv_to_int(primary_value, 0);
                            if (flag == 0)
                            {
                                continue;
                            }
                        }
                    
                    // var old_dr = worksheet.Row(0);
                    // if (IsPrimaryKeyZero(old_dr, primary_key))
                    // {
                    //     BdUtils.log("--- primary_key:", primary_key);
                    //     continue;
                    // }
                    //
                    // BdUtils.log("~~~ primary_key:", primary_key);
                    
                    for (int col = startCol; col <= endCol; col++)
                    {
                        var cell = worksheet.Cells[row, col];
                        var value = cell.Value;

                        bool isDateTime = BdUtils.get_type(value) == "System.DateTime";
                        if (isDateTime)
                        {
                            value = ((DateTime)value).ToString("yyyy-MM-dd hh:mm:ss");
                            // BdUtils.log("isDateTime --- ", isDateTime, "---", header_i, value, BdUtils.get_type(value));
                        }

                        dr[col - startCol] = value;
                    }
                    dt.Rows.Add(dr);
                }

                if (primary_key != null)
                {
                    if (BdUtils.is_integer(primary_key))
                        dt.PrimaryKey = new DataColumn[] { dt.Columns[BdUtils.conv_to_int(primary_key)] };
                    else
                    {
                        BdUtils.assert(dt.Columns.Contains(primary_key.ToString()), $"表格[{dt.TableName}]指定主键列名[{primary_key.ToString()}]时出错!");
                        dt.PrimaryKey = new DataColumn[] { dt.Columns[primary_key.ToString()] };
                    }
                    // BdUtils.log(dt.Columns.Contains(primary_key.ToString()));
                    // BdUtils.log("primary_key --- ", primary_key, BdUtils.get_type(primary_key), "~~~", dt.PrimaryKey, dt.Columns);
                }
                return dt;
            }
        }
        
        // 检查 DataRow 的主键值是否为0
        static bool IsPrimaryKeyZero(DataRow dr, object primary_key)
        {
            // // 确保 DataRow 拥有一个表格
            // if (dr.Table == null || dr == null)
            //     return false;

            // 获取主键列的值
            object primaryKeyValue;
            if (BdUtils.is_string(primary_key))
            {
                string primaryKeyColumnName = BdUtils.conv_to_string(primary_key);
                primaryKeyValue = dr[primaryKeyColumnName];
                BdUtils.log($"--- primary_key.primaryKeyColumnName: {primaryKeyColumnName}, primaryKeyValue: {primaryKeyValue}");
            }
            else
            {
                BdUtils.assert(BdUtils.is_integer(primary_key), "必须为整数!");
                primaryKeyValue = dr[BdUtils.conv_to_int(primary_key)];
            }

            BdUtils.log($"--- primary_key.primaryKeyValue: {primaryKeyValue}");
            // object primaryKeyValue = dr[primaryKeyColumnName];

            // 判断主键值是否为0
            if (primaryKeyValue != null && primaryKeyValue != DBNull.Value)
            {
                int primaryKeyIntValue;
                if (int.TryParse(primaryKeyValue.ToString(), out primaryKeyIntValue))
                {
                    return primaryKeyIntValue == 0;
                }
            }

            return false;
        }

        public static int CountChineseChars(string input)
        {
            int count = 0;
            Regex regex = new Regex("[\u4e00-\u9fa5]");
            MatchCollection matches = regex.Matches(input);
            count = matches.Count;
            return count;
        }

        public static DataTable sort_data_table(DataTable dt, string ordering)
        {
            dt.DefaultView.Sort = ordering;
            DataTable ret = dt.DefaultView.ToTable();
            return ret;
        }

        public static List<string> table_extension_ls = new List<string>()
        {
            ".csv", ".xls", ".xlsx"
        };
        
        public static List<string> excel_extension_ls = new List<string>()
        {
            ".xls", ".xlsx"
        };
        
        public static string change_file_extension(string f_path, string new_extension)
        {
            string dir_path = Path.GetDirectoryName(f_path);
            var (file_name_0, file_extension) = BdUtils.get_file_name_and_extension(f_path);
            
            string _new_extension = BdUtils.complete_file_extension(new_extension);
            string new_file_name = $"{file_name_0}{_new_extension}";
            
            string res = BdUtils.join(dir_path, new_file_name);
            return res;
        }

        public static DataTable read_table(string f_path, string primary_key = null, Dictionary<string, System.Type> type_dc = null, bool priority_persistent_data = false)
        {
            f_path = BdUtils.get_relative_sub_path(BdUtils.streaming_data_dir_path, f_path);
            
            string new_f_path = change_file_extension(f_path, ".csv");
            DataTable ret;
            if (!BdTools.Flags.is_unity_editor)
            {
                BdUtils.log($"=== TableUtils.read_table --- new_f_path: {new_f_path}");
                if (!BdTools.Flags.is_android_or_webgl)
                {
                    var _new_f_path = BdUtils.join(BdUtils.data_table_dir_path, new_f_path);
                    BdUtils.assert(BdUtils.exists_file(_new_f_path), $"csv表格不存在? f_path: {_new_f_path}");
                }
                ret = _read_table(new_f_path, primary_key: primary_key, type_dc: type_dc, priority_persistent_data: priority_persistent_data);
            }
            else
            {
                ret = _read_table(f_path, primary_key: primary_key, type_dc: type_dc, priority_persistent_data: priority_persistent_data);
            }
            
            if (BdTools.Flags.is_unity_editor)
            {
                // BdPackage.BdUtils.log("~~~~~~~ is_unity_editor!", f_path);
                if (BdUtils.check_file_extension(f_path, excel_extension_ls, raise_error: false))
                {
                    // FileInfo fileInfo = new FileInfo(f_path);
                    // // 获取文件的最后修改时间
                    // DateTime lastModified = fileInfo.LastWriteTime;
                    
                    var _new_f_path = BdUtils.join(BdUtils.data_table_dir_path, new_f_path);
                    // BdPackage.BdUtils.log("~~~~~~~ excel_extension_ls!", $", f_path: [{f_path}] --> _new_f_path: [{_new_f_path}]");

                    // --- 这里最好根据修改时间来判断是否需要覆盖, 但留待以后完善了.
                    save_to_csv(ret, _new_f_path);
                }
            }
            return ret;
        }
        
        public static bool is_exists_table(string f_path, bool raise_error = false)
        {
            string dir_path = Path.GetDirectoryName(f_path);
            string dir_name = Path.GetFileName(dir_path);

            var total_file_name = Path.GetFileName(f_path); 
            var (file_name_0, file_extension) = BdUtils.get_file_name_and_extension(f_path);
            BdUtils.log($"~~~ dir_path: {dir_path}, dir_name: {dir_name}, file_name_0: {file_name_0}, file_extension: {file_extension}");

            
            bool is_null_extension = BdUtils.is_null(file_extension);
            if (is_null_extension)
            {
                BdUtils.log($"文件后缀为空? total_file_name: {total_file_name}");
                BdUtils.assert(!raise_error, $"文件路径不存在! f_path: {f_path}");
                return false;
            }
            else if (BdUtils.check_file_extension(total_file_name, table_extension_ls))
            {
                if (BdUtils.exists_file(f_path))
                    return true;
                else
                    return false;
            }
            else
            {
                BdUtils.log($"不知名的后缀名? file_extension: {file_extension}");
                return false;
            }
            // BdUtils.assert(false, "?????");
            // return false;
        }
        
        public static void show_data_table(DataTable dt, int zh_len = 5, string select = null)
        {

            int max_len = zh_len * 2;

            BdUtils.assert(max_len >= 6, "max_len最小为6!");
            BdUtils.assert(max_len % 2 == 0, "为了兼容中文字符, max_len必须为偶数!");

            string my_format_str = "{0,-" + max_len + "} ";
            string column_str = "";

            foreach (DataColumn column in dt.Columns)
            {
                var _name = column.ColumnName;

                column_str += string.Format(my_format_str, _name);

                int n_zh = TableUtils.CountChineseChars(_name);
                column_str = column_str.Substring(0, column_str.Length - n_zh);

                // column_str += "|";
            }
            // BdUtils.log(column_str);


            string row_str = "";

            // var rows = dt.Rows;

            List<object> rows;
            if (select != null)
                rows = BdUtils.conv_obj_to_list(dt.Select(select));
            else
                rows = BdUtils.conv_obj_to_list(dt.Rows);


            foreach (DataRow row in rows)
            {
                for (int i = 0; i < dt.Columns.Count; i++)
                {
                    string _r = row[i].ToString();

                    int n_zh = CountChineseChars(_r);

                    if (n_zh > 0 && _r.Length + n_zh > max_len)
                    {
                        int _i = 0;
                        int _len = 0;
                        while (_len <= max_len - 2)
                        {
                            var _s = _r.Substring(_i, 1);
                            if (CountChineseChars(_s) == 1)
                                _len += 2;
                            else
                                _len += 1;
                            _i += 1;
                        }

                        string _end;

                        try
                        {
                            _end = String.Concat(System.Linq.Enumerable.Repeat(" ", n_zh));
                        }
                        catch (Exception e)
                        {
                            BdUtils.log("*** n_zh: ", n_zh, " --- _r:", _r);
                            throw new Exception($"*** error: {e}");
                        }

                        BdUtils.assert(_i > 0, "`max_len`设置的太短了会报错的...");
                        _r = _r.Substring(0, _i - 1) + ".." + _end;
                    }

                    else if (_r.Length > max_len)
                    {
                        _r = _r.Substring(0, max_len - 2) + "..";
                    }

                    row_str += string.Format(my_format_str, _r);
                    row_str = row_str.Substring(0, row_str.Length - n_zh);

                    // row_str += "|";
                }

                row_str += "\n";
            }

            BdPackage.BdUtils.log($"{column_str}\n{row_str}");
        }

        public static void show_one_row(DataRow row1)
        {
            DataTable dt = row1.Table;
            foreach (DataColumn column in dt.Columns)
            {
                var _name = column.ColumnName;
                BdUtils.log(_name, row1[_name]);
            }
        }

        // --- 指定列名, 该列将转换为list
        public static List<T> conv_one_col_to_ls<T>(DataRow[] rows, string col_name)
        {


            // List<T> ret = dataTable.AsEnumerable()
            //                    .Select(row => row.Field<T>(columnName))
            //                    .ToList();
            // string col_name = "序号";
            var ret = new List<T>();

            foreach (var row_i in rows)
                // ret.Add((T)row_i[col_name]);
                ret.Add((T)Convert.ChangeType(row_i[col_name], typeof(T)));
            return ret;
        }

        public static List<Dictionary<string, T>> conv_rows_to_dc_ls<T>(DataRow[] rows)
        {
            var ret = new List<Dictionary<string, T>>();

            foreach (var row_i in rows)
                ret.Add(conv_row_to_dc<T>(row_i));
            return ret;
        }

        public static Dictionary<string, T> conv_row_to_dc<T>(DataRow row1)
        {

            Dictionary<string, T> dc = new Dictionary<string, T>();

            if (row1 == null)
                return dc;

            DataTable dt = row1.Table;

            foreach (DataColumn column in dt.Columns)
            {
                string key = column.ColumnName;
                // T value = (T)row1[key];
                T value = (T)Convert.ChangeType(row1[key], typeof(T));

                dc.Add(key, value);
            }
            return dc;
        }

        public static Dictionary<string, object> conv_row_to_dc(DataRow row1)
        {

            var dc = conv_row_to_dc<object>(row1);
            return dc;
        }
        
        /// <summary>
        /// 指定某一列(该列必须为int类型), 获取该列可用的一个index
        /// </summary>
        /// <param name="dt">表</param>
        /// <param name="col_name">列名</param>
        /// <param name="ignore_gap">是否可以在间隙中取值</param>
        /// <param name="min_index">指定ls中最小的值从哪开始</param>
        /// <returns></returns>
        public static int get_available_index_for_selected_column(DataTable dt, string col_name, bool ignore_gap = false, int min_index = BdUtils.DEFAULT_INT)
        {
            // --- 找到一个可用的背包格子索引, 例如`ID`列[1, 2, 3] -> 4; 若ignore_gap为`false`, 则[1, 3, 4] -> 2; 否则返回5;
            var slot_rows = dt.Select("", $"{col_name} DESC");
            var slot_ls = TableUtils.conv_one_col_to_ls<int>(slot_rows, $"{col_name}");
            
            int max_i = slot_ls.Max();

            if (ignore_gap)
                return max_i + 1;

            int min_i = slot_ls.Min();
            if (min_index != BdUtils.DEFAULT_INT)
                min_i = Mathf.Min(min_index, min_i);
            
            // BdUtils.log($"--- col_name: {col_name}, max_i: {max_i}, min_i: {min_i}", $"--- slot_ls:", slot_ls);

            int res = -1;
            for (int i = min_i + 1; i <= max_i + 1; i++)
            {
                if (!slot_ls.Contains(i))
                {
                    res = i;
                    break;
                }
            }
            return res;
        }

        public static DataRow get(DataTable dt, object pk = null, int? index = null, string select = null, bool show_data = false)
        {
            // BdUtils.log(" DataTable --- ", dt);
            // BdUtils.assert(dt != null, $"dt[{dt.TableName}]不能为null!");
            BdUtils.assert(dt != null, $"dt不能为null!");
            DataRow row1 = null;

            if (index != null)
            {
                int _index = (int)index;
                if (_index < dt.Rows.Count)
                    row1 = dt.Rows[_index];
            }
            else if (pk != null)
            {
                BdUtils.assert(dt.PrimaryKey.Length, $"*** datatable[{dt.TableName}]没有主键!");
                // row1 = dt.Rows[(int)BdUtils.conv_to_int(pk)];
                row1 = dt.Rows.Find(pk);
            }
            else if (select != null)
            {
                var rs = dt.Select(select);
                if (rs.Length >= 1)
                    row1 = rs[0];
            }
            else
                BdUtils.assert(0, "index, pk和select不能同时为空!");

            if (show_data)
            {
                show_one_row(row1);
            }
            return row1;
        }

        public static void remove_rows(DataTable dt, DataRow[] rows)
        {
            foreach (var row in rows)
            {
                dt.Rows.Remove(row);
            }
        }

        // --- 获取子表
        public static DataTable new_sub_table(DataTable dt, DataRow[] rows = null, string[] cols = null)
        {
            DataTable dt2;
            if (cols != null && cols.Length > 0)
                dt2 = dt.DefaultView.ToTable(false, cols);
            else
                dt2 = dt.Clone();

            if (rows != null && rows.Length > 0)
            {
                foreach (DataRow row in rows)
                {
                    dt2.ImportRow(row);
                }
            }
            return dt2;
        }

        // public static DataTable swap_row(DataTable dt, int[] row_indexes)
        // {
        //     return dt;
        // }

        public static DataTable new_type_table(DataTable dt, Dictionary<string, System.Type> type_dc = null)
        {
            // Create a new DataTable to hold the selected columns and types
            DataTable dt2 = new DataTable();

            // If no columns were selected, return an empty DataTable
            if (type_dc == null || type_dc.Count == 0)
            {
                return dt.Clone();
            }

            // Add the selected columns and types to the new DataTable
            foreach (var _columnName in dt.Columns)
            {
                string columnName = _columnName.ToString();
                System.Type columnType = typeof(string);
                if (type_dc.ContainsKey(columnName))
                {
                    // Add the column to the new DataTable with the selected type
                    // dt2.Columns.Add(columnName, columnType);
                    // BdUtils.log("_columnName --- ", columnName);
                    columnType = type_dc[columnName];
                }
                dt2.Columns.Add(columnName, columnType);
            }

            // Copy the rows from the original DataTable to the new DataTable
            foreach (DataRow row in dt.Rows)
            {
                // Create a new row in the new DataTable
                DataRow newRow = dt2.NewRow();

                var dc = conv_row_to_dc(row);
                foreach (var kv in dc)
                {
                    string columnName = kv.Key;
                    object v = kv.Value;
                    System.Type columnType = v.GetType();

                    if (type_dc.ContainsKey(columnName))
                    {
                        columnType = type_dc[columnName];
                        // Convert the value to the selected type and set it in the new row
                    }
                    newRow[columnName] = Convert.ChangeType(row[columnName], columnType);
                }

                // Add the new row to the new DataTable
                dt2.Rows.Add(newRow);
            }

            if (dt.PrimaryKey.Length != 0)
            {
                int pk_i = dt2.Columns.IndexOf(dt.PrimaryKey[0].ToString());
                if (pk_i != -1)
                    dt2.PrimaryKey = new DataColumn[] { dt2.Columns[pk_i] };
            }

            dt2.TableName = dt.TableName;

            // Return the new DataTable with selected columns and types
            return dt2;
        }

        public static DataTable swap_row(DataTable dt, int row1Index, int row2Index)
        {
            // --- 交换table中两行的位置
            if (row1Index < 0 || row1Index >= dt.Rows.Count ||
                row2Index < 0 || row2Index >= dt.Rows.Count)
            {
                throw new ArgumentException("The row indexes must be within the bounds of the DataTable.");
            }

            DataRow dr = dt.NewRow();

            dr.ItemArray = dt.Rows[row1Index].ItemArray;

            DataColumn[] primaryKeyColumns = dt.PrimaryKey;
            dt.PrimaryKey = null;

            dt.Rows[row1Index].ItemArray = dt.Rows[row2Index].ItemArray;
            dt.Rows[row2Index].ItemArray = dr.ItemArray;

            dt.PrimaryKey = primaryKeyColumns;
            return dt;
        }

        // public void save_2(DataTable dt, string fpath, ExcelSaveAsFileType fileFormat)
        // {
        //     // Create a new Excel package
        //     using (var package = new ExcelPackage())
        //     {
        //         // Create a new worksheet in the Excel package
        //         var worksheet = package.Workbook.Worksheets.Add("Sheet1");

        //         // Fill the worksheet with the data from the DataTable
        //         worksheet.Cells["A1"].LoadFromDataTable(dt, true);

        //         // Save the Excel package to a file with the specified file format and UTF-8 encoding
        //         var file = new FileInfo(fpath);
        //         using (var fileStream = new FileStream(fpath, FileMode.Create))
        //         {
        //             package.SaveAs(file, fileFormat);
        //         }
        //     }
        // }

        public static void save_to_csv(DataTable dataTable, string fpath)
        {
            try
            {
                // Create a StreamWriter to write the CSV file
                // 创建不带有BOM的UTF-8编码器
                UTF8Encoding utf8NoBom = new UTF8Encoding(false);
                
                // using (StreamWriter writer = new StreamWriter(fpath, false, Encoding.UTF8))
                using (StreamWriter writer = new StreamWriter(fpath, false, utf8NoBom))
                {
                    int dt_column_count = dataTable.Columns.Count;
                    int _i = 1;
                    foreach (DataColumn column in dataTable.Columns)
                    {
                        writer.Write(column.ColumnName);
                        if (_i != dt_column_count)
                            writer.Write(",");
                        _i++;
                    }
                    writer.WriteLine();

                    // Write the data rows
                    _i = 1;
                    foreach (DataRow row in dataTable.Rows)
                    {
                        foreach (var item in row.ItemArray)
                        {
                            string item_str = QuoteValue(item.ToString());
                            writer.Write(item_str);
                            
                            if (_i != dt_column_count)
                                writer.Write(",");
                            _i++;
                        }
                        writer.WriteLine();
                    }
                }

                Console.WriteLine("DataTable saved to CSV file successfully.");
            }
            catch (Exception ex)
            {
                Console.WriteLine("An error occurred while saving the DataTable to CSV file: " + ex.Message);
            }
        }
        
        private static string QuoteValue(string value)
        {
            // 如果数据中包含逗号，引号，换行符或者制表符，则需要用双引号括起来，并且内部的双引号需要用两个双引号表示
            if (value.Contains(",") || value.Contains("\"") || value.Contains("\n") || value.Contains("\r") || value.Contains("\t"))
                return "\"" + value.Replace("\"", "\"\"") + "\"";

            return value;
        }

        public static void save_to_excel(DataTable dt, string fpath)
        {
            BdUtils.assert(File.Exists(fpath), $"文件路径fpath`{fpath}`不存在?");

            if (BdUtils.check_file_extension(fpath, ".csv", raise_error: false))
            {
                save_to_csv(dt, fpath);
                return;
            }

            BdUtils.check_file_extension(fpath, excel_extension_ls);

            var file = new FileInfo(fpath);

            string sheet_name = "Sheet";

            using (ExcelPackage pck = new ExcelPackage(file))
            {
                var ls = BdUtils.conv_obj_to_list<string>(pck.Workbook.Worksheets);
                // BdUtils.log("--- ls:", ls, "--- Contains:", ls.Contains(sheet_name));

                if (ls.Contains(sheet_name))
                {
                    // BdUtils.log("~~~ Delete!");
                    pck.Workbook.Worksheets.Delete(sheet_name);
                }

                ExcelWorksheet ws = pck.Workbook.Worksheets.Add(sheet_name);
                ws.Cells["A1"].LoadFromDataTable(dt, true);
                pck.Save();
            }

            // using (var fileStream = new FileStream(fpath, FileMode.Create))
            // {

            // }
            // // Create a new Excel package
            // using (var package = new ExcelPackage())
            // {
            //     // Create a new worksheet in the Excel package
            //     var worksheet = package.Workbook.Worksheets.Add("Sheet1");

            //     // Fill the worksheet with the data from the DataTable
            //     worksheet.Cells["A1"].LoadFromDataTable(dt, true);

            //     // Save the Excel package to a file using UTF-8 encoding
            //     // using (var fileStream = new FileStream(fpath, FileMode.Create))
            //     // {
            //     //     package.SaveAs(fileStream, new UTF8Encoding(false));
            //     // }

            //     // // Save the Excel package to a file using UTF-8 encoding
            //     // var file = new FileInfo(fpath);
            //     // using (var fileStream = new FileStream(fpath, FileMode.Create))
            //     // {
            //     //     package.SaveAs(file, new UTF8Encoding(false));
            //     // }
            //     // Save the Excel package to a file using UTF-8 encoding
            //     // using (var fileStream = new FileStream(fpath, FileMode.Create))
            //     // {
            //     //     package.SaveAs(fileStream);
            //     //     File.WriteAllText(fpath, File.ReadAllText(fpath), new UTF8Encoding(false));
            //     // }

            //     // using (ExcelPackage pck = new ExcelPackage(fpath))
            //     // {
            //     //     ExcelWorksheet ws = pck.Workbook.Worksheets.Add("Accounts");
            //     //     ws.Cells["A1"].LoadFromDataTable(dataTable, true);
            //     //     pck.Save();
            //     // }
            // }
        }

        public DataTable GetPagedTable(DataTable dt, int p, int page_size)//PageIndex表示第几页，PageSize表示每页的记录数
        {
            if (p == -1)
                return dt; // -1页代表每页数据，直接返回

            BdUtils.assert(p > 0, "页码`p`必须为正整数!");
            BdUtils.assert(page_size > 0, "每页数量`page_size`必须为正整数!");

            DataTable newdt = dt.Copy();
            newdt.Clear();//copy dt的框架

            int rowbegin = (p - 1) * page_size;
            int rowend = p * page_size;

            if (rowbegin >= dt.Rows.Count)
                return newdt;//源数据记录数小于等于要显示的记录，直接返回dt

            if (rowend > dt.Rows.Count)
                rowend = dt.Rows.Count;
            for (int i = rowbegin; i <= rowend - 1; i++)
            {
                DataRow newdr = newdt.NewRow();
                DataRow dr = dt.Rows[i];
                foreach (DataColumn column in dt.Columns)
                {
                    newdr[column.ColumnName] = dr[column.ColumnName];
                }
                newdt.Rows.Add(newdr);
            }
            return newdt;
        }
    }

    public class FileEncodingUtils
    {
        # region 文件编码
        
        /// <summary>
        /// 获取文件的编码格式
        /// </summary>
        public static Encoding GetEncoding(string file_name)
        {
            Encoding r = Encoding.UTF8;
            try
            {
                FileStream fs = new FileStream(file_name, FileMode.Open, FileAccess.Read);
                r = GetEncoding(fs);
                fs.Close();
            }
            catch (Exception e)
            {
                BdPackage.BdUtils.log($"*** 解析 file_name[{file_name}] 的csv编码时发送错误! 将使用`utf-8`编码!,\nerror: {e}");
            }
            return r;
        }
 
        /// <summary>
        /// 通过给定的文件流，判断文件的编码类型
        /// </summary>
        /// <param name="fs">文件流</param>
        /// <returns>文件的编码类型</returns>
        private static Encoding GetEncoding(FileStream fs)
        {
            //文件的字符集在Windows下有两种，一种是ANSI，一种Unicode。
            //对于Unicode，Windows支持了它的三种编码方式，一种是小尾编码（Unicode)，一种是大尾编码(BigEndianUnicode)，一种是UTF - 8编码。
            //byte[] Unicode = new byte[] { 0xFF, 0xFE };
            //byte[] UnicodeBIG = new byte[] { 0xFE, 0xFF };
            //byte[] UTF8 = new byte[] { 0xEF, 0xBB, 0xBF }; //BOM头
 
            if (fs.Length < 3)
                return Encoding.Default;
 
            byte[] bytes = new byte[3];
            fs.Read(bytes, 0, 3);

            Encoding reVal = Encoding.GetEncoding("GB2312");
 
            if (bytes[0] == 0xFE && bytes[1] == 0xFF)
            {
                reVal = Encoding.BigEndianUnicode;
            }
            else if (bytes[0] == 0xFF && bytes[1] == 0xFE)
            {
                reVal = Encoding.Unicode;
            }
            else
            {
                if (!(bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF))
                {
                    fs.Position = 0;
                }
                if (IsUTF8Bytes(fs))
                {
                    if (bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF)
                        reVal = new UTF8Encoding(false);
                    else
                        reVal = Encoding.UTF8;
                }
            }
 
            return reVal;
        }
 
        private static byte UTF8_BYTE_MASK = 0b1100_0000;
        private static byte UTF8_BYTE_VALID = 0b1000_0000;
        private static bool IsUTF8Bytes(FileStream fs)
        {
            //BinaryReader r = new BinaryReader(fs);
            byte[] bytes = new byte[1];
            fs.Read(bytes, 0, 1);
 
            //1字节 0xxxxxxx
            //2字节 110xxxxx 10xxxxxx
            //3字节 1110xxxx 10xxxxxx 10xxxxxx
            //4字节 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
            //5字节 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
            //6字节 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
            while (fs.Read(bytes, 0, 1) > 0)
            {
                if (bytes[0] < 0x80)
                    continue;
 
                int cnt = 0;
                byte b = bytes[0];
                while ((b & 0b1000_0000) != 0)
                {
                    cnt++;
                    b <<= 1;
                }
                cnt -= 1;
 
                for (int i = 0; i < cnt; i++)
                {
                    if (fs.Read(bytes, 0, 1) <= 0)
                        return false;
                    if ((bytes[0] & UTF8_BYTE_MASK) != UTF8_BYTE_VALID)
                        return false;
                }
            }
 
            return true;
        }
        # endregion
        
    }
    public class MyClient
    {
        public static void run()
        {
            // string dir_path = BdUtils.join(BdUtils.streamingAssetsPath, "data");
            string dir_path = BdUtils.data_table_dir_path;

            // --- 读取excel
            // string file_name = "test_save_to_excel.xlsx";
            // string file_name = "红色动态表.xls";

            // string f_path = BdUtils.join(dir_path, file_name);
            // DataTable dt = TableUtils.read_excel(f_path, primary_key: "序号");
            // DataTable dt = TableUtils.read_excel(f_path);  // 没有主键, 不推荐
            // DataTable dt = TableUtils.read_excel(f_path, primary_key: 0);  // 第0列作为主键

            // --- 读取csv
            // string file_name = "图书表.csv";
            string file_name = "上传路径选项.csv";
            string f_path = BdUtils.join(dir_path, file_name);
            // DataTable dt = TableUtils.read_csv(f_path);  // 没有主键, 不推荐
            DataTable dt = TableUtils.read_csv(f_path, primary_key: "ID");

            # region 相关操作

            // --- 指定列的类型读取
            // DataRow r0 = TableUtils.get(dt, 1);
            // BdUtils.log("old ---", r0["ID"], BdUtils.get_type(r0["ID"]));

            // Dictionary<string, System.Type> type_dc = new Dictionary<string, System.Type>();
            // type_dc.Add("ID", typeof(int));
            // type_dc.Add("age", typeof(int));
            // dt = TableUtils.new_type_table(dt, type_dc);

            // DataRow r1 = TableUtils.get(dt, 1);
            // BdUtils.log("new ---", r1["ID"], BdUtils.get_type(r1["ID"]));

            // dt = TableUtils.sort_data_table(dt, "ID desc");

            // --- 列操作
            // List<string> l2 = BdUtils.conv_obj_to_list<string>(dt.Columns);
            // // var col_ls = l2.Take(3).ToArray();
            // var col_ls = l2.GetRange(0, 1).ToArray();
            // // var col_ls = l2.GetRange(2, 3).ToArray();

            // BdUtils.log(" === col_ls:", col_ls);
            // // List<string> l2 = l1.ConvertAll<string>(x => x.ToString());

            // DataTable dt2 = TableUtils.new_sub_table(dt, cols: col_ls);

            // // DataTable dt2 = dt.DefaultView.ToTable(false, col_ls);

            // // BdUtils.log(" === dt2.col_name:", dt2.Columns[0]);
            // // foreach (var row in dt2.Select())
            // //     BdUtils.log(" ~~~", TableUtils.conv_row_to_dc<int>(row));

            // // BdUtils.log("--- dc_ls:", TableUtils.conv_rows_to_dc_ls<string>(dt2.Select()));
            // // BdUtils.log("--- dc_ls:", TableUtils.conv_one_col_to_ls<int>(dt2.Select(), dt2.Columns[0].ToString()));
            // BdUtils.log("--- dc_ls:", TableUtils.conv_one_col_to_ls<string>(dt.Select(), "类型"));
            // BdUtils.log("--- dc_ls:", BdUtils.conv_obj_to_list<string>(new string[] { "a", "1", "b" }));
            // DataTableColumnToList(dt,

            // BdUtils.log(" === col_ls:", BdUtils.conv_obj_to_list(col_ls));

            // DataTable dt2 = TableUtils.new_sub_table(dt, dt.Select());

            // var rows = dt.Select("类型 = '参观须知'");
            // dt.Rows.Remove(row);
            // dt.Rows.RemoveAt(0);
            // TableUtils.remove_rows(dt, rows);

            // TableUtils.show_data_table(dt2);

            // --- 行和列的展示
            // BdUtils.log(" --- col:", dt.Columns.Count, BdUtils.conv_obj_to_list(dt.Columns));
            // var r_dc = TableUtils.conv_row_to_dc(row);
            // BdUtils.log(" --- row:", dt.Rows.Count, r_dc);

            //  --- 交换两行

            TableUtils.show_data_table(dt);

            TableUtils.swap_row(dt, 0, 1);

            // TableUtils.swap_row()

            // int row1Index = 1;
            // int row2Index = 2;

            // DataRow row1 = dt.Rows[row1Index];
            // DataRow row2 = dt.Rows[row2Index];

            // object temp = row1;


            // --- 子表
            // DataTable dt2 = TableUtils.new_sub_table(dt, dt.Select("类型 = '参观须知'", "序号 desc"));
            // DataTable dt2 = TableUtils.new_sub_table(dt, dt.Select("类型 = '参观须知'"));
            // TableUtils.show_data_table(dt2, zh_len: 10);

            // --- 排序
            // dt = TableUtils.sort_data_table(dt, "序号 desc");
            // dt = TableUtils.sort_data_table(dt, "发布时间 desc");

            // --- get 单一查找
            // DataRow row1 = dt.Rows.Find("2");
            // DataRow row1 = TableUtils.get(dt, 3);

            // --- select 批量查找
            // var rs = dt.Select("ID <= 2");
            // BdUtils.log(rs[1]);
            // BdUtils.ReflectionUtils.show_properties(rs);

            // DataRow row1 = TableUtils.get(dt, select: "ID=1");
            // DataRow row1 = TableUtils.get(dt, pk: 1);
            // var row1 = rs[0];

            // --- 展示一行
            // TableUtils.show_one_row(row1);


            // --- 类似`pandas`的`df.apply`
            // var rows = dt.Select("ID <= 2");
            // // var r = TableUtils.get(dt, 2);  // , r["ID"]
            // BdUtils.log("--- rows len:", rows.Length, BdUtils.get_type(rows[0]["ID"]));
            // // Define a delegate to perform the operation on each row
            // Func<DataRow, int> rowSum = delegate (DataRow row)
            // {
            //     int sum = 0;
            //     var dc = TableUtils.conv_row_to_dc(row);
            //     sum += (int)BdUtils.conv_to_int(dc["印刷页码"], 0);
            //     sum += (int)BdUtils.conv_to_int(dc["物理页码"], 0);
            //     return sum;
            // };

            // foreach (DataRow row in rows)
            // {
            //     var dc = TableUtils.conv_row_to_dc(row);

            //     int sum = rowSum(row);
            //     BdUtils.log("--- ID: ", dc["ID"], "Row sum: " + sum, "--- 印刷页码:", dc["印刷页码"], "--- 物理页码:", dc["物理页码"]);
            // }

            // var dc = TableUtils.conv_row_to_dc(row1);

            // // // string colname = "序号";
            // // // string colname = "作者";
            // string colname = "发布时间";
            // var res = BdUtils.conv_to_int(dc[colname]);
            // BdUtils.log("\n --- ", colname, dc[colname], res, res == null, BdUtils.is_null(res), BdUtils.get_type(res));

            # endregion

            TableUtils.show_data_table(dt, zh_len: 10);

            // BdUtils.log("table_name --- ", dt.TableName);
        }

        public static void test_datatable()
        {

            // 创建 DataTable
            DataTable dt = new DataTable();

            // 添加列
            dt.Columns.Add("Name", typeof(string));
            dt.Columns.Add("Age", typeof(int));
            dt.Columns.Add("Gender", typeof(string));

            dt.PrimaryKey = new DataColumn[] { dt.Columns["Name"] };

            // 添加行
            dt.Rows.Add("John", 25, "Male");
            dt.Rows.Add("Mike", 32, "Male");
            dt.Rows.Add("Emily", 29, "Female");

            // 遍历 DataTable
            foreach (DataRow row in dt.Rows)
            {
                Console.WriteLine("Name: " + row["Name"]);
                Console.WriteLine("Age: " + row["Age"]);
                Console.WriteLine("Gender: " + row["Gender"]);
            }

            BdUtils.log("************* Find ***");
            DataRow row1 = dt.Rows.Find("Mike");
            // DataRow row1 = dt.Rows.Find(29);

            Console.WriteLine("Name: " + row1["Name"]);
            Console.WriteLine("Age: " + row1["Age"]);
            Console.WriteLine("Gender: " + row1["Gender"]);

            BdUtils.log("************* Select ***");
            DataRow[] result = dt.Select("Age = 32 or Gender = 'Female'");
            foreach (DataRow row in result)
            {
                Console.WriteLine("Name: " + row["Name"]);
                Console.WriteLine("Age: " + row["Age"]);
                Console.WriteLine("Gender: " + row["Gender"]);
                BdUtils.log(BdUtils.end_string);
            }
        }

    }
}
